﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Spanner
{
    /// <summary>
    /// Many things in Spanner are concerned with displaying expressions and so forth as JavaScript and/or HTML.
    /// This class abstracts that behaviour to make things simpler.
    /// </summary>
    internal class FmtTerm: ITerm
    {
        internal ITerm[] Children;

        internal FmtTerm(params ITerm[] children) { Children = children; }

        void ITerm.Write(Context ctxt, JSorHtml what)
        {
            if (Children == null) return;
            foreach (var x in Children) x.Write(ctxt, what);
        }

        IEnumerable<IVar> ITerm.Dependencies()
        {
            return Children.SelectMany(x => (x is IVar) ? new IVar[] { (IVar)x } : ((ITerm)x).Dependencies());
        }
    }

    internal class FmtSeparated : FmtTerm, ITerm
    {
        internal string LPar = "(";
        internal string RPar = ")";
        internal string Sep = ", ";

        internal FmtSeparated(string lPar, string rPar, string sep, params ITerm[] children) {
            LPar = lPar;
            RPar = rPar;
            Sep = sep;
            Children = children;
        }

        void ITerm.Write(Context ctxt, JSorHtml what)
        {
            ctxt.WritePlain(LPar);
            if (Children != null)
            {
                for (var i = 0; i < Children.Length; i++)
                {
                    if (0 < i) ctxt.WritePlain(Sep);
                    Children[i].Write(ctxt, what);
                }
            }
            ctxt.WritePlain(RPar);
        }

        IEnumerable<IVar> ITerm.Dependencies()
        {
            return Children.SelectMany(x => ((ITerm)x).Dependencies());
        }
    }

    internal class FmtParens : FmtSeparated
    {
        internal FmtParens(params ITerm[] children) : base("(", ")", ", ", children) { }
    }

    internal class FmtBrackets : FmtSeparated
    {
        internal FmtBrackets(params ITerm[] children) : base("[", "]", ", ", children) { }
    }

    internal class FmtBraces : FmtSeparated
    {
        internal FmtBraces(params ITerm[] children) : base("{", "}", ", ", children) { }
    }

    internal class FmtVar : FmtTerm, ITerm
    {
        internal IVar X;

        internal bool Deref;

        internal FmtVar(IVar x, bool deref = true) { X = x; Deref = deref; }

        void ITerm.Write(Context ctxt, JSorHtml what)
        {
            ctxt.WriteIVar(what, X, Deref);
        }

        IEnumerable<IVar> ITerm.Dependencies()
        {
            yield return X;
        }
    }

    internal class FmtStr : FmtTerm, ITerm
    {
        internal string S;

        internal FmtStr(string s) { S = s; }

        void ITerm.Write(Context ctxt, JSorHtml what)
        {
            ctxt.WritePlain(S);
        }

        IEnumerable<IVar> ITerm.Dependencies() { yield break; }
    }

    internal class FmtIndentation : FmtTerm, ITerm
    {
        void ITerm.Write(Context ctxt, JSorHtml what)
        {
            if (what == JSorHtml.JS) ctxt.WriteIndentation();
        }

        IEnumerable<IVar> ITerm.Dependencies() { yield break; }
    }

    internal class FmtNewline : FmtTerm, ITerm
    {
        void ITerm.Write(Context ctxt, JSorHtml what)
        {
            if (what == JSorHtml.JS) ctxt.WriteLine();
        }

        IEnumerable<IVar> ITerm.Dependencies() { yield break; }
    }

    internal class FmtIndented : FmtTerm, ITerm
    {
        internal FmtIndented(params ITerm[] children) { Children = children; }

        void ITerm.Write(Context ctxt, JSorHtml what)
        {
            if (Children == null || Children.Length == 0)
            {
                ctxt.WriteLine();
                return;
            }

            ctxt.Indentation++;
            foreach (var x in Children) x.Write(ctxt, what);
            ctxt.Indentation--;
        }
    }

    internal class FmtOneLiner: FmtTerm, ITerm {

        internal const char EscChar = '#';

        internal string Fmt;
        
        void ITerm.Write(Context ctxt, JSorHtml what) {
            var j = 0;
            var n = Fmt.Length;
            var i = 0;
            Func<char, bool> nextCharIs = x => i + 1 < n && Fmt[i + 1] == x;
            for (i = 0; i < n; i++)
            {
                var x = Fmt[i];
                if (x == EscChar && !nextCharIs(EscChar))
                {
                    ctxt.WriteTerm(Children[j++], what);
                }
                else if (x == '{' && nextCharIs('}'))
                {
                    ctxt.WritePlain("{}");
                    i++;
                }
                else if (x == '{' && what == JSorHtml.JS)
                {
                    ctxt.WritePlain("{");
                    ctxt.WriteLine();
                    ctxt.Indentation++;
                }
                else if (x == '}' && what == JSorHtml.JS)
                {
                    ctxt.Indentation--;
                    ctxt.WriteIndentation();
                    ctxt.WritePlain("}");
                }
                else
                {
                    ctxt.WritePlain(x.ToString());
                }
            }
        }

        IEnumerable<IVar> ITerm.Dependencies()
        {
            return Children.Where(x => x != null)
                           .SelectMany(x => (x is IVar) ? new IVar[] { (IVar)x } : ((ITerm)x).Dependencies());
        }
    }

}
